// Copyright (C) 2022 Linlexiao
#include "calculate.h"

#include <QDebug>
#include <algorithm>
#include <iostream>

Result::Result() {
    han = 0, fu = 0;
    point1 = 0, point2 = 0;
    pointType = OYATSUMO;
    yaku = QVector<QString>();
    isYakuman = false;
    manType = NOMANGAN;
}

void Calculator::_calculateYaku(HandSet hand, Result& res) {
    QString msg = "";
    for (auto p : hand.blocks) {
        for (auto pp : p.getPai()) {
            msg += pp.toString();
        }
        msg += " ";
    }
    msg += "雀头:";
    for (auto p : hand.pair.getPai()) {
        msg += p.toString();
    }
    qDebug() << msg;
    res.yaku.clear();
    // 2.排除役满
    int cnt = 0;
    for (auto x : yakumanYakus) {
        int p = x->test(hand);
        cnt += p;
        //        qDebug()<<x->getName()<<p;
        if (p != 0) {
            res.yaku.push_back(x->getName());
            res.isYakuman = true;
        }
    }
    //    qDebug()<<"isYakuman?"<<res.isYakuman;
    if (res.isYakuman) {
        res.han = cnt;
        return;
    }
    // 4.计算普通役种
    for (auto x : yakus) {
        int p = x->test(hand);

        cnt += p;
        if (p != 0) {
            res.yaku.push_back(
                QString("%1: %2番").arg(x->getName(), QString::number(p)));
        }
    }
    // TODO 计算番缚
    res.han = cnt;
}

void Calculator::_calculateFu(HandSet hand, Result& res) {
    //特判1：平和
    if (Pinfu().test(hand) == 1 && hand.flag.testFlag(HandFlag::TSUMO)) {
        res.fu = 20;
        return;
    }

    //符底
    int fu = 20;
    // 和牌方式
    if (hand.flag.testFlag(HandFlag::TSUMO)) fu += 2;
    if (hand.flag.testFlag(HandFlag::RON) &&
        hand.flag.testFlag(HandFlag::MENZEN))
        fu += 10;
    //听牌方式
    if (hand.type == MachiType::DAN_QI || hand.type == MachiType::KAN_ZHANG ||
        hand.type == MachiType::BIAN_ZHANG) {
        fu += 2;
    }
    //面子符
    for (auto b : hand.blocks) {
        if (b.blockType == BlockType::SEQUENCE) continue;
        int baseFu = 2;
        if (!b.isOpen) baseFu *= 2;
        if (b.consistYao()) baseFu *= 2;
        if (b.blockType == BlockType::QUAD) baseFu *= 4;
        fu += baseFu;
    }
    //雀头符
    fu += hand.pair.getPai()[0].isYakuhai(hand.flag) * 2;
    // 特判2：副露平和
    if (fu == 20) {
        fu = 30;
    }
    // 切上
    if (fu % 10 != 0) {
        fu = (fu + 10) / 10 * 10;
    }
    res.fu = fu;
}

void Calculator::_calculatePoint(Result& x) {
    int& han = x.han;
    int& fu = x.fu;
    auto& flag = nowHandSet.flag;
    x.pointType = PointType(0 | ((int)(!flag.testFlag(SEAT_EAST)) << 1) |
                            flag.testFlag(RON));
    auto& type = x.pointType;
    int basePoint = 0;
    //首先排除役满
    if (x.isYakuman) {
        basePoint = 8000 * x.han;
    }
    //先判断满贯以上
    else if ((han == 3 && fu >= 70) || (han == 4 && fu >= 40) || han == 5) {
        //满贯
        x.manType = MANGAN;
        basePoint = 2000;
    } else if (han == 6 || han == 7) {
        //跳满
        x.manType = HANEMAN;
        basePoint = 3000;
    } else if (8 <= han && han <= 10) {
        //倍满
        x.manType = BAIMAN;
        basePoint = 4000;
    } else if (11 <= han && han <= 12) {
        //三倍满
        x.manType = SANBAIMAN;
        basePoint = 6000;
    } else if (han >= 13) {
        //累计役满
        x.manType = KAZOEYUAKUMAN;
        basePoint = 8000;
    } else {
        x.manType = NOMANGAN;
        //用公式计算
        basePoint = fu * pow2[han + 2];
        if (han == 0) basePoint = 0;
    }

    switch (type) {
        case OYATSUMO:
            //每家收两份基本点
            x.point1 = 2 * basePoint;
            break;
        case OYARON:
            x.point1 = 6 * basePoint;
            break;
            //收六分基本点
        case KOTSUMO:
            //庄家两份 子家一份
            x.point1 = basePoint;
            x.point2 = 2 * basePoint;
            break;
        case KORON:
            //收四分
            x.point1 = 4 * basePoint;
            break;
    }

    //切上
    if (x.point1 % 100 != 0) {
        x.point1 = (x.point1 + 100) / 100 * 100;
    }
    if (x.point2 % 100 != 0) {
        x.point2 = (x.point2 + 100) / 100 * 100;
    }
}

void Calculator::_calculateKokushi() {
    int cnt[20] = {0};
    bool isAgari[20] = {false};
    bool isKokuShimuSou = true;
    for (auto p : nowP) {  //获得所有的幺九牌数量
        if (!p.isYao()) isKokuShimuSou = false;
        int x;
        switch (p.type) {
            case MAN:
                //从第8个开始
                x = p.num;
                if (x == 9) x = 2;
                if (p.isAgari) isAgari[7 + x] = true;
                cnt[7 + x]++;
                break;
            case SUO:
                //从第10个开始
                x = p.num;
                if (p.isAgari) isAgari[9 + x] = true;
                if (x == 9) x = 2;
                cnt[9 + x]++;
                break;
            case PIN:
                //从第12个开始
                x = p.num;
                if (x == 9) x = 2;
                if (p.isAgari) isAgari[11 + x] = true;
                cnt[11 + x]++;
                break;
            case ZI:
                if (p.isAgari) isAgari[p.num] = true;
                cnt[p.num]++;
                break;
        }
    }
    int yakumanCount = 0;
    for (int i = 1; i <= 13; i++) {
        if (cnt[i] == 0) {
            isKokuShimuSou = false;
            break;
        }
        if (cnt[i] == 2 && isAgari[i]) yakumanCount++;
    }
    if (isKokuShimuSou) {
        yakumanCount++;
        Result kokushi;
        kokushi.han = yakumanCount;
        kokushi.fu = 0;
        kokushi.isYakuman = true;
        kokushi.yaku.push_back((yakumanCount == 1) ? QString("国士无双")
                                                   : QString("国士无双十三面"));

        if (nowHandSet.flag.testFlag(HandFlag::TENHOU)) {
            kokushi.han++;
        }
        if (nowHandSet.flag.testFlag(HandFlag::CHIIHOU)) {
            kokushi.han++;
        }
        _calculatePoint(kokushi);
        result = kokushi;
    }
}

void Calculator::_calculateChiitui() {
    if (nowHandSet.flag.testFlag(MENZEN) &&
        nowP.length() == 14) {  //门前限定（同时确保一定14张牌 不会数组越界）
        bool isChituitsu = true;
        for (int i = 0; i < 7; i++) {
            // a.如果不能构成对子
            if (!(nowP[i * 2] == nowP[i * 2 + 1])) {
                isChituitsu = false;
                break;
            }
            // b.如果是龙七对
            if (i != 0) {  //第一组不用判断
                if (nowP[i * 2] == nowP[i * 2 - 1]) {
                    isChituitsu = false;
                    break;
                }
            }
        }
        if (isChituitsu) {
            int cnt = 2;
            int yakuman = 0;
            QVector<QString> yakuName = {"七对子: 2番"};
            QVector<QString> yakumanName;
            // a.计算悬赏牌
            {
                int dora = 0;
                int ura = 0;
                int akadora = 0;
                for (auto p : nowP) {
                    for (auto d : nowHandSet.dora) {
                        if (p == d.next()) dora++;
                    }
                    if (nowHandSet.flag.testFlag(RIICHI)) {
                        for (auto d : nowHandSet.ura) {
                            if (p == d.next()) ura++;
                        }
                    }
                    if (p.isRed) akadora++;
                }
                if (dora) {
                    yakuName.push_back(
                        QString("%1: %2番").arg("宝牌", QString::number(dora)));
                }
                if (ura) {
                    yakuName.push_back(
                        QString("%1: %2番")
                            .arg("里宝牌", QString::number(ura)));
                }
                if (akadora) {
                    yakuName.push_back(
                        QString("%1: %2番")
                            .arg("赤宝牌", QString::number(akadora)));
                }
                cnt += dora + ura + akadora;
            }
            // b.计算立直及偶然役种
            {
#define FLAGYAKU(n, p)                                            \
    {                                                             \
        int x = n().test(nowHandSet);                             \
        if (x > 0) {                                              \
            cnt += x;                                             \
            yakuName.push_back(                                   \
                QString("%1: %2番").arg(#p, QString::number(x))); \
        }                                                         \
    }

                FLAGYAKU(Riichi, 立直);
                FLAGYAKU(MenzenTsumo, 门前清自摸和);
                FLAGYAKU(Ippatsu, 一发);
                FLAGYAKU(ChanKan, 抢杠);
                FLAGYAKU(RinshanKaihou, 岭上开花);
                FLAGYAKU(HouteiRaoyui, 河底摸鱼);
                FLAGYAKU(HaiteiRaoyue, 海底捞月);
                FLAGYAKU(DoubleRiichi, 双立直);

                int x = Tenhou().test(nowHandSet);
                if (x) {
                    yakuman += x;
                    yakumanName.push_back("天和");
                }
                x = Chiihou().test(nowHandSet);
                if (x) {
                    yakuman += x;
                    yakumanName.push_back("地和");
                }
            }
            // c.计算手牌役种
            {
                //断幺九
                {
                    bool flag = true;
                    for (auto p : nowP) {
                        if (p.isYao()) {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) {
                        cnt++;
                        yakuName.push_back(
                            QString("%1: %2番")
                                .arg("断幺九", QString::number(1)));
                    }
                }
                //混一色
                {
                    int typeCnt[5] = {0};
                    for (auto p : nowP) {
                        typeCnt[p.type] = 1;
                    }
                    if (typeCnt[MAN] + typeCnt[SUO] + typeCnt[PIN] == 1 &&
                        typeCnt[ZI] == 1) {
                        cnt += 3;
                        yakuName.push_back(
                            QString("%1: %2番")
                                .arg("混一色", QString::number(3)));
                    }
                }
                //清一色
                {
                    int typeCnt[5] = {0};
                    for (auto p : nowP) {
                        typeCnt[p.type] = 1;
                    }
                    if (typeCnt[MAN] + typeCnt[SUO] + typeCnt[PIN] == 1 &&
                        typeCnt[ZI] == 0) {
                        cnt += 6;
                        yakuName.push_back(
                            QString("%1: %2番")
                                .arg("清一色", QString::number(6)));
                    }
                }
                //混老头
                {
                    bool flag = true;
                    for (auto p : nowP) {
                        if (!p.isYao()) {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) {
                        cnt += 2;
                        yakuName.push_back(
                            QString("%1: %2番")
                                .arg("混老头", QString::number(2)));
                    }
                }
                //字一色
                {
                    bool flag = true;
                    for (auto p : nowP) {
                        if (p.type != ZI) {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) {
                        yakuman++;
                        yakumanName.push_back("字一色");
                    }
                }
            }
            Result chituiResult;
            if (yakuman) {
                chituiResult.han = yakuman;
                chituiResult.fu = 0;
                chituiResult.yaku = yakumanName;
                chituiResult.isYakuman = true;
            } else {
                chituiResult.han = cnt;
                chituiResult.fu = 25;
                chituiResult.yaku = yakuName;
            }
            _calculatePoint(chituiResult);
            result = chituiResult;
        }
    }
}

void Calculator::_calculateNormal(int dep) {
    if (nowP.length() == 0) {  //找到和牌形了
        Result newResult;
        _calculateFu(nowHandSet, newResult);
        _calculateYaku(nowHandSet, newResult);
        _calculatePoint(newResult);
        //        qDebug()<<result.fu<<"符"<<result.han<<"番";
        //        qDebug()<<result.yaku;
        if (newResult.point1 > result.point1 ||
            (newResult.point1 == result.point1 &&
             newResult.point2 > result.point2)) {
            result = newResult;
        }
        return;
    }
    if (dep == 0) {  //找雀头
        int l = nowP.length();
        for (int i = 0; i < l - 1; i++) {
            if (nowP[i] == nowP[i + 1]) {
                Pai a = nowP[i];
                Pai b = nowP[i + 1];
                if (a.isAgari || b.isAgari) {
                    nowHandSet.type =
                        DAN_QI;  // 如果和了牌在雀头中，则为单骑听牌
                }
                nowHandSet.pair =
                    Pair(a.type, a.num, (int)a.isRed + (int)b.isRed);
                nowP.remove(i, 2);
                _calculateNormal(dep + 1);
                nowP.insert(i, a);
                nowP.insert(i + 1, b);
            }
        }
    } else {  //找面子
        {
            Pai a = nowP[0];
            Pai b = nowP[1];
            Pai c = nowP[2];  //能组成刻子
            if (a == b && b == c) {
                bool open = false;
                if (a.isAgari || b.isAgari || c.isAgari) {
                    nowHandSet.type = SHUANG_PENG;
                    if (nowHandSet.flag.testFlag(RON))
                        open = true;  //荣和是明刻
                }
                nowHandSet.blocks.push_back(
                    Block(TRIPLET, a.type, a.num, open,
                          (int)a.isRed + (int)b.isRed + (int)c.isRed));
                nowP.remove(0, 3);
                _calculateNormal(dep + 1);
                nowP.insert(0, a);
                nowP.insert(1, b);
                nowP.insert(2, c);
                nowHandSet.blocks.pop_back();
            }
        }
        {
            int l = nowP.length();
            //顺子
            if (nowP[0].num > 7 ||
                nowP[0].type == ZI) {  //无法构成顺子，说明此分解方法错误
                return;
            }
            Pai a = nowP[0];
            Pai a2 = a.next();
            Pai a3 = a2.next();

            for (int i = 1; i < l; i++) {
                for (int j = i + 1; j < l; j++) {  // O(n^2)需改进
                    if (nowP[i] == a2 && nowP[j] == a3) {
                        a2 = nowP[i];
                        a3 = nowP[j];
                        nowHandSet.blocks.push_back(Block(
                            SEQUENCE, a.type, a.num, false,
                            (int)a.isRed + (int)a2.isRed + (int)a3.isRed));
                        if (a.isAgari) {
                            if (a.num == 7)
                                nowHandSet.type = BIAN_ZHANG;
                            else
                                nowHandSet.type = LIANG_MIAN;
                        } else if (a3.isAgari) {
                            if (a3.num == 3)
                                nowHandSet.type = BIAN_ZHANG;
                            else
                                nowHandSet.type = LIANG_MIAN;
                        } else if (a2.isAgari) {
                            nowHandSet.type = KAN_ZHANG;
                        }

                        nowP.remove(j);
                        nowP.remove(i);
                        nowP.remove(0);
                        _calculateNormal(dep + 1);
                        nowP.insert(0, a);
                        nowP.insert(i, a2);
                        nowP.insert(j, a3);
                        nowHandSet.blocks.pop_back();
                    }
                }
            }
        }
    }
}

Result Calculator::calculate(const State& state) {
    nowHandSet = HandSet();
    result = Result();
    // 1.分离出所有的牌型
    nowP = state.pais;
    nowP.push_back(state.agariPai);
    std::sort(nowP.begin(), nowP.end());
    QString msg;
    for (auto x : nowP) {
        msg += x.toString();
    }
    qDebug() << "牌" << msg;
    nowHandSet.flag = state.flag;
    nowHandSet.dora = state.dora;
    nowHandSet.ura = state.ura;
    nowHandSet.agariPai = state.agariPai;
    for (auto b : state.furu) {
        nowHandSet.blocks.push_back(b);
    }
    _calculateKokushi();
    _calculateChiitui();
    _calculateNormal(0);
    return result;
}

Calculator::Calculator() {
    yakus = {
        new MenzenTsumo(),
        new TanYao(),
        new YakuhaiFieldEast(),
        new YakuhaiFieldSouth(),
        new YakuhaiFieldWest(),
        new YakuhaiFieldNorth(),
        new YakuhaiSeatEast(),
        new YakuhaiSeatSouth(),
        new YakuhaiSeatWest(),
        new YakuhaiSeatNorth(),
        new YakuhaiHako(),
        new YakuhaiHatsu(),
        new YakuhaiCyuu(),
        new ChanKan(),
        new RinshanKaihou(),
        new HaiteiRaoyue(),
        new HouteiRaoyui(),
        new Riichi(),
        new Iipeikou(),
        new Pinfu(),
        new Ippatsu(),
        new Dora(),
        new Ura(),
        new AkaDora(),
        new Toitoi(),
        new Sanankou(),
        new SanshokuDoukou(),
        new Sankantsu(),
        new Shousangen(),
        new Honroutou(),
        new DoubleRiichi(),
        new SanshokuDoujun(),
        new Ikkitsuukan(),
        new Chantaiyao(),
        new Honiisou(),
        new Junchantaiyao(),
        new Ryanpeikou(),
        new Chiniisou(),
    };
    yakumanYakus = {
        new Daisangen(),     new Suuankou(),
        new SuuankouTanki(), new Shousuushi(),
        new Daisuushi(),     new Tsuuiisou(),
        new Ryuuiisou(),     new Chinroutou(),
        new ChuurenPoutou(), new JunseiChuurenPoutou(),
        new Suukantsu(),     new Tenhou(),
        new Chiihou(),
    };
    pow2[0] = 1;
    for (int i = 1; i < 20; i++) {
        pow2[i] = pow2[i - 1] << 1;
    }
}
